package fregat.connector.base;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.util.Enumeration;
import java.util.Vector;

import fregat.connector.ConnectorService;
import fregat.base.util.ConfigurationBundle;
import fregat.base.util.Logger;

/**
 * Loads and stores the configuration of a file that should be read or written
 * and handles all operations on that file.
 */
public class FileHandler {
  private boolean standardIO; // flag, that communication is handled on standard IO
  private Vector dataFiles; // enumeration of dataFiles
  private FileDescription controlFile; // name of corresponding controlFileName file
  private Vector dataFilesCopy; // enumeration of dataFiles
  private FileDescription controlFileCopy; // name of corresponding controlFileName file
  private String faultMessageCopy;  // Stoermeldung
  private boolean fault; // liegt eine Stoerung bei copyFiles vor oder nicht
  private boolean copyInProgress;  
  private long timeout; // timeout between checking for controlFileName file
  private boolean deleteDataFile; // flag, that file should be deleted after reading
  private boolean ready; // flag, that file is ready for read or write
  private boolean backup; // flag, that backup is enabled
  private String category; // backup category name
  private boolean controlFileExists;
  private FTPTransfer ftp;
  private boolean reset;
  private String direct;

  private static final String STD_IN = "STDIN";
  private static final String STD_OUT = "STDOUT";

  public static final String IN = "in";
  public static final String OUT = "out";


  public FileHandler(String name, String direction, FTPTransfer ftp) {
    String debug = null;
    String configPath = "channel.config." + name + "." + direction;
    int key = (direction == IN ? 1 : 3); // key for text resources
    ConfigurationBundle configuration = ConnectorService.getConfiguration();

    this.ftp = ftp;

    this.direct = direction;

    Enumeration e = configuration.getConfigurationEnumeration(configPath, ",", ConfigurationBundle.STRING);

    while (e.hasMoreElements()) {
      String path = (String) e.nextElement();

      if (path.equalsIgnoreCase(STD_IN) || path.equalsIgnoreCase(STD_OUT)) {
        // read from standard in or write to standard out
        standardIO = true;
      } else {
        // read from data file(s)
        if (dataFiles == null) {
          dataFiles = new Vector();
        }
        dataFiles.addElement(new FileDescription(path));
      }
    }

    if (dataFiles != null) {
      // control file
      String path = configuration.getConfigurationString(configPath + ".control");
      debug = ConnectorService.resource.getString("FileHandler_constructor_" + key);
      debug = debug.replaceFirst("%1", logFiles());

      if (path != null) {
        controlFile = new FileDescription(path);
        timeout = configuration.getConfigurationLong(configPath + ".control.timeout");
        if (timeout < 0) {
          timeout = 5000;
        }
        deleteDataFile = !configuration.getConfigurationBoolean(configPath + ".keep");
        debug += ConnectorService.resource.getString("FileHandler_constructor_5")
            .replaceFirst("%1", controlFile.toString())
            .replaceFirst("%2", Long.toString(timeout));
      }

      // backup
      backup = (configuration.getConfigurationString(configPath + ".backup.file") != null);

      if (backup) {
        category = name + "." + direction + ".backup";
        configPath = configPath.substring(0, configPath.lastIndexOf('.'));

        configuration.configureLogger("channel.config", category);
        Logger.getCategory(category).hide(true, true, true);
      }

      ConnectorService.debug(debug);
      if (direction.equalsIgnoreCase(OUT)) {
        configPath = "channel.config." + name + "." + direction;
        Enumeration f = configuration.getConfigurationEnumeration(configPath + ".copy", ",", ConfigurationBundle.STRING);

        while (f.hasMoreElements()) {
          String pathCopy = (String) f.nextElement();
          if (dataFilesCopy == null) {
            dataFilesCopy = new Vector();
          }
          dataFilesCopy.addElement(new FileDescription(pathCopy));
        }
        if (dataFilesCopy != null) {
          String pathControl = configuration.getConfigurationString(configPath + ".copy.control");
          faultMessageCopy = configuration.getConfigurationString(configPath + ".copy.faultMessage");
          debug = ConnectorService.resource.getString("FileHandler_constructor_" + key);
          debug = debug.replaceFirst("%1", logFilesCopy());
          if (pathControl != null) {
            controlFileCopy = new FileDescription(pathControl);
            debug += ConnectorService.resource.getString("FileHandler_constructor_6")
                .replaceFirst("%1", controlFileCopy.toString());
          }
          ConnectorService.debug(debug);
        }
      }
    } else {
      debug = ConnectorService.resource.getString("FileHandler_constructor_" + (key + 1));
      ConnectorService.debug(debug);
    }
  }

  public void log(String str) {
    if (backup) {
      if (reset) {
        Logger.getCategory(category).info(Logger.getTimestamp());
        reset = false;
      }
      Logger.getCategory(category).info(str);
    }
  }

  public void reset() {
    reset = true;
  }

  public PrintStream createDataFile() {
    PrintStream out = null;
    try {
      out = new PrintStream(new FileOutputStream(dataFile(), true));
    } catch (IOException ex) {
      ConnectorService.error(ConnectorService.resource.getString("FileHandler_createDataFile_1")
          .replaceFirst("%1", dataFile()));
    }
    return out;
  }

  public boolean isConfigured() {
    return dataFiles != null;
  }

  public boolean writeControlFile() {
    try {
      FileWriter writer = new FileWriter(controlFile.toString());
      writer.write("\n");
      writer.close();
      ConnectorService.debug(ConnectorService.resource.getString("FileHandler_writeControlFile_1")
          .replaceFirst("%1", controlFile.toString()));
      return true;

    } catch (IOException ex) {
      ConnectorService.error(ConnectorService.resource.getString("FileHandler_createControlFile_1")
          .replaceFirst("%1", controlFile.toString()));
      return false;
    }
  }

  public void retrieve() {
    for (int i = 0; i < dataFiles.size(); i++) {
      FileDescription file = (FileDescription) dataFiles.elementAt(i);
      ftp.retrieveFile(file);
    }
  }

  public void store() {
    for (int i = 0; i < dataFiles.size(); i++) {
      FileDescription file = (FileDescription) dataFiles.elementAt(i);
      ftp.storeFile(file);
    }
    ftp.storeFile(controlFile);

    // delete local control file
    deleteControlFile(false);
  }

  public boolean isControlFileExisting() {
    try {
      if (ftp.isConfigured()) {
        controlFileExists = ftp.fileExists(controlFile);
      } else {
        FileReader reader = new FileReader(controlFile.toString());
        controlFileExists = true;
        reader.close();
      }
    } catch (FileNotFoundException ex) {
      controlFileExists = false;
    } catch (IOException ex) {
      ConnectorService.error("FileHandler.isControlFileExisting", ex);
    }
    return controlFileExists;
  }

  public void deleteControlFile(boolean remote) {
    if (ftp.isConfigured() && remote) {
      ftp.deleteFile(controlFile);
    } else {              
      debug("FileHandler_deleteControlFile", controlFile, new File(controlFile.toString()).delete(), false);
    }
  }
  public void deleteDataFile(boolean remote) {
    if (deleteDataFile) {
      for (int i = 0; i < dataFiles.size(); i++) {
        FileDescription dataFile = (FileDescription) dataFiles.elementAt(i);

        // delete remote file
        if (remote && ftp.isConfigured() && ftp.fileExists(dataFile)) {
          ftp.deleteFile(dataFile);
        }

        // delete local file
        File file = new File(dataFile.toString());
        if (file.exists()) {
          debug("FileHandler_deleteDataFile", dataFile, file.delete(), false);
        }
      }
    }
  }


  public boolean writeControlFile( FileDescription ctrlFile ) {
    try {
      FileWriter writer = new FileWriter(ctrlFile.toString());
      writer.write("\n");
      writer.close();
      ConnectorService.debug(ConnectorService.resource.getString("FileHandler_writeControlFile_1")
          .replaceFirst("%1", ctrlFile.toString()));
      return true;

    } catch (IOException ex) {
      ConnectorService.error(ConnectorService.resource.getString("FileHandler_createControlFile_1")
          .replaceFirst("%1", ctrlFile.toString()));
      return false;
    }
  }

  public boolean isControlFileExisting( FileDescription ctrlFile ) {

     boolean fileExists = false;

     try {
       FileReader reader = new FileReader(ctrlFile.toString());
       fileExists = (reader != null);
       reader.close();
     } catch (FileNotFoundException ex) {
       fileExists = false;
     } catch (IOException ex) {
       ConnectorService.error("FileHandler.isControlFileExisting", ex);
     }
     return fileExists;
   }


  public boolean deleteFile( FileDescription fileName ) {

     boolean fileDeleted = new File(fileName.toString()).delete();
     debug("FileHandler_deleteFile", fileName, fileDeleted , false);
     return fileDeleted;
  }

   /**
    * Copy datafile to a second destination and create control file
    */
  private boolean copyFile(FileDescription source, FileDescription destination) {

    boolean fileCopied = false;

    FileChannel sourceChannel = null;
    FileChannel destinationChannel = null;
    FileReader in = null;
    FileWriter out = null;
    try {
      File inputFile = new File(source.toString());
      File outputFile = new File(destination.toString());

      in = new FileReader(inputFile);
      out = new FileWriter(outputFile);
      int c;

      while ((c = in.read()) != -1)
         out.write(c);

      in.close();
      out.close();

      sourceChannel = new FileInputStream( inputFile ).getChannel();
      destinationChannel= new FileInputStream( outputFile ).getChannel();

      if (sourceChannel.size() == destinationChannel.size()) {
        fileCopied = true;
      }
      sourceChannel.close();
      destinationChannel.close();
    } catch (IOException ex ) {
       // ConnectorService.error("ConnectorService.copyFile", ex );
        fileCopied = false;
        try {
          if (in != null) { in.close(); }
          if (out != null) { out.close(); }
          if (sourceChannel != null) { sourceChannel.close(); }
          if (destinationChannel != null) { destinationChannel.close(); }
        } catch (IOException e) {
        }
    }



    return fileCopied;
  }

  /**
   * Copy datafile to a second destination and create control file
   * If a fault situation occurs a fault message will be created which
   * is sent to the database
   */
  private boolean transferFiles() {

    boolean copyState = false;
    FileDescription sourceDataFile;
    FileDescription targetDataFile;

    for (int i = 0; i < dataFiles.size(); i++) {
      sourceDataFile = (FileDescription) dataFiles.elementAt(i);
      targetDataFile = (FileDescription) dataFilesCopy.elementAt(i);
      copyState = this.copyFile( sourceDataFile , targetDataFile ) ;
      if (!copyState) {
        if ( (this.faultMessageCopy == null) && (!fault)) {
          ConnectorService.error(ConnectorService.resource.getString("FileHandler_transferFiles_2")
              .replaceFirst("%1", sourceDataFile.toString() )
                  .replaceFirst("%2", targetDataFile.toString() ));
        }
        break;
      } else {
        ConnectorService.debug(ConnectorService.resource.getString("FileHandler_transferFiles_1")
            .replaceFirst("%1", sourceDataFile.toString() )
            .replaceFirst("%2", targetDataFile.toString() ));
      }
    }

    if ( copyState && (this.isControlled()) ) {
      this.writeControlFile(controlFileCopy) ;
    }

    if (!copyState) {
      if (!fault) {
        if ( this.faultMessageCopy != null ) {
          ConnectorService.faultMessage(this.faultMessageCopy);
        }
        ConnectorService.error(ConnectorService.resource.getString("FileHandler_transferFiles_3"));
        fault = true ;
      }
    } else {
        if (fault) {
          ConnectorService.debug(ConnectorService.resource.getString("FileHandler_transferFiles_4"));
        }
        fault = false;
    }

    return (copyState);
  }

  /**
   * Move datafile to a second destination and create control file
   * Afterwards delete local datafile and control file
   */
  public void moveFiles() {

    if (copyInProgress) return;
    if (!this.direct.equalsIgnoreCase(OUT) ) return;
    if (!this.isControlled()) return;

    if ((dataFilesCopy != null)
        && (controlFileCopy != null)
        && this.isControlFileExisting(controlFile)
        && (!this.isControlFileExisting(controlFileCopy))) {
      copyInProgress = true;
      if (transferFiles()) {
        deleteDataFile(false);
        deleteControlFile(false);
      }
      copyInProgress = false;
    }
  }

  public String logFilesCopy() {
    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < dataFilesCopy.size(); i++) {
      if (i > 0) {
        buffer.append(',');
      }
      buffer.append("'");
      buffer.append(dataFilesCopy.elementAt(i));
      buffer.append("'");
    }
    return buffer.toString();
  }

  private void debug(String key, FileDescription file, boolean deleted, boolean remote) {
    String str;
    if (deleted) {
      ConnectorService.debug(ConnectorService.resource.getString(key + "_1").replaceFirst("%1", file.toString()) + (remote ? "(ftp)" : ""));
    } else {
      ConnectorService.error(ConnectorService.resource.getString(key + "_2").replaceFirst("%1", file.toString()) + (remote ? "(ftp)" : ""));
    }
  }

  public boolean isControlled() {
    return controlFile != null;
  }

  public boolean isReady() {
    return ready;
  }

  public void setReady(boolean ready) {
    this.ready = ready;
  }

  public boolean isStandardIO() {
    return standardIO;
  }

  public synchronized void timeout() {
    try {
      wait(timeout);
    } catch (InterruptedException ex) {
    }
  }

  public synchronized void shutdown() {
    notify();
  }

  private String dataFile() {
    return ((FileDescription) dataFiles.firstElement()).toString();
  }

  public Enumeration dataFiles() {
    return dataFiles.elements();
  }

  public String logFiles() {
    StringBuffer buffer = new StringBuffer();
    for (int i = 0; i < dataFiles.size(); i++) {
      if (i > 0) {
        buffer.append(',');
      }
      buffer.append("'");
      buffer.append(dataFiles.elementAt(i));
      buffer.append("'");
    }
    return buffer.toString();
  }

  public String toString() {
    StringBuffer buffer = new StringBuffer();
    buffer.append(ConnectorService.resource.getString("FileHandler_toString_" + (isReady() ? 3 : 4)));
    if (isControlled()) {
      buffer.append(" (");
      buffer.append(ConnectorService.resource.getString("FileHandler_toString_" + (controlFileExists ? "1" : "2")).replaceFirst("%1", controlFile.toString()));
      buffer.append(")");
    }

    return buffer.toString();
  }
}
